import fs from "fs-extra";
import path from "path";
import { StructDef } from "../typings";
import { ProtocolStructParser } from "./ProtocolStructParser.js";
import { Byte } from "./Byte.js";
import { toolchain } from "../tools/toolchain.js";

enum EBaseType { char, uchar, byte, short, ushort, int, uint, longlong, ulonglong, string, max };

enum ETypeFlagV1 {
    has_count = 0x10000000,
    has_ver = 0x20000000,
    has_refer = 0x40000000,
    has_select = 0x80000000,
    mask = 0xfffffff,
}

enum ETypeFlagV2 {
    has_count = 0x1000000,
    has_ver = 0x2000000,
    has_refer = 0x4000000,
    has_select = 0x8000000,
    safeprintf = 0x10000000,
    mask = 0xffffff,
}

const FIELD_NAME_MAX_LEN = 2048;

export class Xml2CSBytes {

    static write(filePath: string) {

        class NameMgr {
            static names: string[] = [];
            static getNameIdx(name: string): number {
                let nameIdx = this.names.indexOf(name);
                if (nameIdx < 0) {
                    this.names.push(name);
                    nameIdx = this.names.length - 1;
                }
                return nameIdx;
            }
        }

        class TypeMgr {
            static ins = new TypeMgr();

            types: { name: string, offset: number, type: "base" | "struct" }[] = [];

            version = 1;

            constructor() {
                if (toolchain.projCfg.xml2protocol!.useCSBytes === 'YES_V2') {
                    this.version = 2;
                }
                console.log('Xml2CSBytes will build ss.bytes with version ' + this.version);
                for (let i = 0; i < EBaseType.max; i++) {
                    this.types.push({ name: EBaseType[i], offset: 0, type: "base" });
                }
            }

            getType(type: string): number {
                type = this.convertType(type);
                let t = this.types.find(i => i.name == type);
                if (t) return this.types.indexOf(t);

                this.types.push({ name: type, offset: -1, type: "struct" });
                return this.types.length - 1;
            };
            setTypeOffset(type: string, offset: number) {
                type = this.convertType(type);
                let t = this.types.find(i => i.name == type)!;
                console.assert(t.offset < 0);
                t.offset = offset;
            }
            isStructType(type: string): boolean {
                type = this.convertType(type);
                let t = this.types.find(i => i.name == type)!;
                return t.type == "struct";
            }
            serializedType(type: string): boolean {
                type = this.convertType(type);
                let t = this.types.find(i => i.name == type)!;
                return t.offset >= 0;
            }
            private convertType(type: string): string { return type == "String" ? "string" : type; }
        }

        function getMacroValue(value: string): number {
            if (!isNaN(Number(value))) return Number(value);
            let rt = ProtocolStructParser.Instance.getMacro(value)?._attributes.value;
            if (!isNaN(Number(rt))) return Number(rt);
            console.error("failed! 未找到宏定义：" + value);
            process.exit(1);
        }

        let writer = new Byte(new ArrayBuffer(5 * 1024 * 1024));

        function writeStructType(st: StructDef) {
            //writer.writeString(st._attributes.name);
            let valuemapPos = 0;
            let isUnion = st._attributes.class == "union";
            let valueAndFieldMap: { value: number, fieldType: number, fieldNameIdx: number }[] = [];
            if (isUnion) {
                let fieldCount = 0;
                for (let entry of st.entries) {
                    if (entry._attributes.value) fieldCount++;
                }
                writer.writeUint8(1); // union flag
                writer.writeUint32(fieldCount);
                valuemapPos = writer.Pos;
                writer.skip(fieldCount * 2 * 4); // 用来存放value哈希过后的bucket的节点个数和偏移量， {bucketNodesCount, bucketOffset}[]
            } else {
                writer.writeUint8(0); // struct flag
                writer.writeUint32(st.entries.length);
            }

            let attTypes: string[] = [];

            for (let entry of st.entries) {
                let att = entry._attributes;
                if (isUnion && !att.value) {
                    continue;
                }

                const ETypeFlag = TypeMgr.ins.version > 1 ? ETypeFlagV2 : ETypeFlagV1;

                let nameIdx = NameMgr.getNameIdx(att.name);
                let type = TypeMgr.ins.getType(att.type);
                if (type > ETypeFlag.mask) {
                    throw new Error('type overflow!');
                }

                if (TypeMgr.ins.version > 1) {
                    if (att.safeprintf) type = type | ETypeFlagV2.safeprintf;
                }
                if (isUnion) {
                    let count = 0;
                    if (att.size) count = getMacroValue(att.size);
                    if (att.count) count = getMacroValue(att.count);
                    if (count > 0xffff) {
                        console.log("failed! 数组太长:" + st._attributes.name + "." + att.name);
                        process.exit(1);
                    }
                    valueAndFieldMap.push({ value: getMacroValue(att.value!), fieldType: type, fieldNameIdx: (count << 16) | nameIdx });
                } else {
                    writer.writeUint16(nameIdx); // write name_idx

                    if (att.version) type = type | ETypeFlag.has_ver;
                    if (att.count || att.size) type = type | ETypeFlag.has_count;
                    if (att.refer) type = type | ETypeFlag.has_refer;
                    if (att.select) type = type | ETypeFlag.has_select;
                    writer.writeUint32(type); // write type

                    if (att.version) writer.writeUint32(Number(att.version));
                    if (att.count) { writer.writeUint32(getMacroValue(att.count)); };
                    if (att.size) { writer.writeUint32(getMacroValue(att.size)); }
                    if (att.refer) { writer.writeUint16(NameMgr.getNameIdx(att.refer)); }
                    if (att.select) { writer.writeUint16(NameMgr.getNameIdx(att.select)); }
                }
                attTypes.push(att.type);
            }

            for (let attType of attTypes) {
                if (TypeMgr.ins.isStructType(attType) && !TypeMgr.ins.serializedType(attType)) {
                    TypeMgr.ins.setTypeOffset(attType, writer.Pos);
                    writeStructType(ProtocolStructParser.Instance.getStructDef(attType)!);
                }
            }

            if (isUnion) {
                valueAndFieldMap.sort((a, b) => a.value - b.value);

                // 生成hash的bucket节点
                let bucketsOffset = [];
                for (let bucketIdx = 0, size = valueAndFieldMap.length; bucketIdx < size; bucketIdx++) {
                    let bucketNodes = [];
                    for (let vf of valueAndFieldMap) {
                        if ((vf.value % size) == bucketIdx) {
                            bucketNodes.push({ value: vf.value, fieldType: vf.fieldType, fieldNameIdx: vf.fieldNameIdx });
                        }
                    }
                    bucketsOffset.push({ bucketNodesCount: bucketNodes.length, bucketOffset: writer.Pos });
                    for (let node of bucketNodes) {
                        writer.writeUint32(node.value);
                        writer.writeUint32(node.fieldType);
                        writer.writeUint32(node.fieldNameIdx);
                    }
                }

                // 回填 {bucketNodesCount, bucketOffset}[]
                writer.resetPos(valuemapPos);
                for (let vf of bucketsOffset) {
                    writer.writeUint32(vf.bucketNodesCount);
                    writer.writeUint32(vf.bucketOffset);
                }
                writer.restorePos();
            }
        }

        //写入文件头
        writer.writeUint32(TypeMgr.ins.version); // 文件版本
        writer.writeUint32(getMacroValue('VERSION')); // 协议版本
        let typesBytesSizePos = writer.Pos;
        writer.skip(4); // 所有类型的字节长度
        let headSize = writer.Pos;

        const bodyDef = ProtocolStructParser.Instance.getStructDef("CSMsgBody")!;
        writeStructType(bodyDef);
        let typesEndPos = writer.Pos;

        // 回填所有类型的字节长度
        writer.resetPos(typesBytesSizePos);
        writer.writeUint32(typesEndPos - headSize);
        writer.restorePos();

        // 写入字段名names
        writer.writeUint16(NameMgr.names.length); // name个数
        for (let name of NameMgr.names) {
            if ((name.length + 1) > FIELD_NAME_MAX_LEN) throw new Error("协议字段名超长, 字段名" + name);
            writer.writeUTF8String(name, false);
        }

        // 写入structtype在文件中的偏移量
        writer.writeUint16(TypeMgr.ins.types.length); // type个数
        for (let type of TypeMgr.ins.types) {
            writer.writeUint32(type.offset);
        }

        if (!fs.existsSync(path.dirname(filePath))) {
            fs.mkdirSync(path.dirname(filePath));
        }

        fs.writeFileSync(filePath, writer.NewBuffer, { flag: "w" });

    }
}